package com.clean.cleantotal.utils

import android.content.ContentUris
import android.content.Context
import android.content.pm.ApplicationInfo
import android.database.Cursor
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.lifecycle.LifecycleCoroutineScope
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.FileUtils
import com.clean.cleantotal.bean.ChildBean
import com.clean.cleantotal.bean.JunkBean
import com.clean.cleantotal.bean.JunkGroup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File

class ScanJunkUtils {

    private var docJob: ScanFileUtil? = null
    private var tempJob: ScanFileUtil? = null
    private var apkJob: ScanFileUtil? = null
    private var otherJob: ScanFileUtil? = null
    private var emptyJob: ScanFileUtil? = null

    private var isDestroy = false


    /**
     * 扫描垃圾
     */
    fun scanJunk(
        lifecycleScope: LifecycleCoroutineScope,
        context: Context,
        one: (junks: List<JunkBean>) -> Unit,
        two: (junks: List<JunkBean>) -> Unit,
        three: (junks: List<JunkBean>) -> Unit,
        callBack: (junks: List<JunkGroup>) -> Unit
    ) {
        val cleanGroups = ArrayList<JunkGroup>()
        cleanGroups.add(JunkGroup("Cache Garbage... ", 0L))
        cleanGroups.add(JunkGroup("Uninstall Leftovers... ", 0L))
        cleanGroups.add(JunkGroup("Temporary Files... ", 0L))
        cleanGroups.add(JunkGroup("Useless Package... ", 0L))

        scanCacheJunk(lifecycleScope, context) {
            updateGroup(0, it, cleanGroups)
            one.invoke(it)
            scanDoc(lifecycleScope) {
                updateGroup(1, it, cleanGroups)
                two.invoke(it)
                scanTemp(lifecycleScope) {
                    updateGroup(2, it, cleanGroups)
                    three.invoke(it)
                    scanApk(lifecycleScope) {
                        updateGroup(3, it, cleanGroups)
                        callBack.invoke(cleanGroups)
                    }
                }
            }
        }
    }


    /**
     * 扫描大文件
     */
    fun scanLargeFiles(
        lifecycleScope: LifecycleCoroutineScope,
        context: Context,
        callBack: (junks: List<JunkGroup>) -> Unit
    ) {
        val largeGroups = ArrayList<JunkGroup>()
        largeGroups.add(JunkGroup("Picture ", 0L))
        largeGroups.add(JunkGroup("Video ", 0L))
        largeGroups.add(JunkGroup("Other files ", 0L))

        var imageEnd = false
        var videoEnd = false
        var otherEnd = false
        loadPhotos(context, lifecycleScope) {
            imageEnd = true
            updateGroup(0, it, largeGroups)
            if (imageEnd && videoEnd && otherEnd) {
                callBack.invoke(largeGroups)
            }
        }
        loadVideos(context, lifecycleScope) {
            videoEnd = true
            updateGroup(1, it, largeGroups)
            if (imageEnd && videoEnd && otherEnd) {
                callBack.invoke(largeGroups)
            }
        }
        scanOther {
            otherEnd = true
            updateGroup(2, it, largeGroups)
            if (imageEnd && videoEnd && otherEnd) {
                callBack.invoke(largeGroups)
            }
        }

    }

    /**
     * 扫描空文件
     */
    fun scanEmptyFiles(
        lifecycleScope: LifecycleCoroutineScope, callBack: (junks: List<JunkBean>) -> Unit
    ) {
        emptyJob?.stop()
        val results = ArrayList<JunkBean>()
        emptyJob?.stop()
        emptyJob = ScanFileUtil(ScanFileUtil.externalStorageDirectory).apply {
            val filter = ScanFileUtil.FileFilterBuilder().addCustomFilter { dir, _ ->
                dir.length() == 0L
            }.onlyScanFile().scanApkFiles().scanLogFiles().scanDocumentFiles().scanTxTFiles()
                .scanZipFiles().build()
            //设置过滤规则
            setCallBackFilter(filter)
        }
        emptyJob?.setScanFileListener(object : ScanFileUtil.ScanFileListener {
            override fun scanBegin() {
                results.clear()
            }

            override fun scanComplete(timeConsuming: Long) {
                lifecycleScope.launch(Dispatchers.IO) {
                    delay(3000 - timeConsuming)
                    withContext(Dispatchers.Main) {
                        if (!isDestroy) {
                            callBack.invoke(results)
                        }
                    }
                }
            }

            override fun scanningCallBack(file: File) {
                if (file.canWrite()) {
                    var drawable: Drawable? = null
                    if (file.path.endsWith(".apk")) {
                        drawable = AppUtils.getApkInfo(file.path)?.icon
                    }
                    results.add(
                        JunkBean(
                            file.path, file.name, file.length(), drawable = drawable
                        )
                    )
                }
            }
        })
        emptyJob?.startAsyncScan()
    }


    private fun updateGroup(index: Int, list: List<JunkBean>, groups: List<JunkGroup>) {
        groups[index].sublist = updateIndex(list)
        groups[index].title += "(${list.size})"
        groups[index].totalSize = list.sumOf { it.size }
    }

    private fun updateIndex(list: List<JunkBean>): ArrayList<JunkBean> {
        if (list.isNotEmpty()) {
            if (list.size == 1) {
                list.first().index = 1
            } else if (list.size >= 2) {
                list.first().index = 0
                list.last().index = -1
            }
        }
        list.forEach {
            it.needUpdate = true
        }
        return list as ArrayList<JunkBean>
    }

    fun destroy() {
        docJob?.stop()
        tempJob?.stop()
        apkJob?.stop()
        otherJob?.stop()
        emptyJob?.stop()
        isDestroy = true
    }

    private fun scanDoc(
        lifecycleScope: LifecycleCoroutineScope, callBack: (junks: List<JunkBean>) -> Unit
    ) {
        val results = ArrayList<JunkBean>()
        docJob?.stop()
        docJob = ScanFileUtil(ScanFileUtil.externalStorageDirectory).apply {
            //设置过滤规则
            val filter = ScanFileUtil.FileFilterBuilder().scanDocumentFiles().onlyScanFile().build()
            //设置过滤规则
            setCallBackFilter(filter)
        }
        //设置回调
        docJob?.setScanFileListener(object : ScanFileUtil.ScanFileListener {
            override fun scanBegin() {
                results.clear()
            }

            override fun scanComplete(timeConsuming: Long) {
                lifecycleScope.launch(Dispatchers.IO) {
                    delay(3000 - timeConsuming)
                    withContext(Dispatchers.Main) {
                        if (!isDestroy) {
                            callBack.invoke(results)
                        }
                    }
                }
            }

            override fun scanningCallBack(file: File) {
                if (file.canWrite()) {
                    results.add(JunkBean(file.path, file.name, file.length()))
                }
            }
        })
        //启动扫描
        docJob?.startAsyncScan()
    }

    private fun scanTemp(
        lifecycleScope: LifecycleCoroutineScope, callBack: (junks: List<JunkBean>) -> Unit
    ) {
        val results = ArrayList<JunkBean>()
        tempJob?.stop()
        tempJob = ScanFileUtil(ScanFileUtil.externalStorageDirectory).apply {
            //设置过滤规则
            val filter = ScanFileUtil.FileFilterBuilder().scanLogFiles().onlyScanFile().build()
            //设置过滤规则
            setCallBackFilter(filter)
        }
        //设置回调
        tempJob?.setScanFileListener(object : ScanFileUtil.ScanFileListener {
            override fun scanBegin() {
                results.clear()
            }

            override fun scanComplete(timeConsuming: Long) {
                lifecycleScope.launch(Dispatchers.IO) {
                    delay(3000 - timeConsuming)
                    withContext(Dispatchers.Main) {
                        if (!isDestroy) {
                            callBack.invoke(results)
                        }
                    }
                }
            }

            override fun scanningCallBack(file: File) {
                if (file.canWrite()) {
                    results.add(JunkBean(file.path, file.name, file.length()))
                }
            }
        })
        //启动扫描
        tempJob?.startAsyncScan()
    }

    private fun scanApk(
        lifecycleScope: LifecycleCoroutineScope, callBack: (junks: List<JunkBean>) -> Unit
    ) {
        val results = ArrayList<JunkBean>()
        apkJob?.stop()
        apkJob = ScanFileUtil(ScanFileUtil.externalStorageDirectory).apply {
            //设置过滤规则
            val filter = ScanFileUtil.FileFilterBuilder().scanApkFiles().onlyScanFile().build()
            //设置过滤规则
            setCallBackFilter(filter)
        }
        //设置回调
        apkJob?.setScanFileListener(object : ScanFileUtil.ScanFileListener {
            override fun scanBegin() {
                results.clear()
            }

            override fun scanComplete(timeConsuming: Long) {
                lifecycleScope.launch(Dispatchers.IO) {
                    delay(3000 - timeConsuming)
                    withContext(Dispatchers.Main) {
                        if (!isDestroy) {
                            callBack.invoke(results)
                        }
                    }
                }
            }

            override fun scanningCallBack(file: File) {
                if (file.canWrite()) {
                    var drawable: Drawable? = null
                    if (file.path.endsWith(".apk")) {
                        drawable = AppUtils.getApkInfo(file.path)?.icon
                    }
                    results.add(
                        JunkBean(
                            file.path, file.name, file.length(), drawable = drawable
                        )
                    )
                }
            }
        })
        //启动扫描
        apkJob?.startAsyncScan()
    }

    private fun scanOther(callBack: (junks: List<JunkBean>) -> Unit) {
        val results = ArrayList<JunkBean>()
        otherJob?.stop()
        otherJob = ScanFileUtil(ScanFileUtil.externalStorageDirectory).apply {
            val filter = ScanFileUtil.FileFilterBuilder().addCustomFilter { dir, _ ->
                dir.length() > 3 * 1024 * 1024
            }.onlyScanFile().scanApkFiles().scanLogFiles().scanDocumentFiles().scanZipFiles()
                .build()
            //设置过滤规则
            setCallBackFilter(filter)
        }
        otherJob?.setScanFileListener(object : ScanFileUtil.ScanFileListener {
            override fun scanBegin() {
                results.clear()
            }

            override fun scanComplete(timeConsuming: Long) {
                callBack.invoke(results)
            }

            override fun scanningCallBack(file: File) {
                if (file.canWrite()) {
                    var drawable: Drawable? = null
                    if (file.path.endsWith(".apk")) {
                        drawable = AppUtils.getApkInfo(file.path)?.icon
                    }
                    results.add(
                        JunkBean(
                            file.path, file.name, file.length(), drawable = drawable
                        )
                    )
                }
            }
        })
        otherJob?.startAsyncScan()
    }

    private fun scanCacheJunk(
        lifecycleScope: LifecycleCoroutineScope,
        context: Context,
        callBack: (ArrayList<JunkBean>) -> Unit
    ) {
        val time = System.currentTimeMillis()
        lifecycleScope.launch(Dispatchers.IO) {
            val list = arrayListOf<String>()
            context.packageManager.getInstalledPackages(0).filter { pkInfo ->
                (pkInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 0
            }.forEach { pkInfo ->
                list.add(pkInfo.packageName)
            }

            val scanList = ArrayList<JunkBean>()
            list.forEach { packageName ->
                val icon = AppUtils.getAppIcon(packageName)
                val appName = AppUtils.getAppName(packageName)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                    // Android 11
                    val child = scanCacheJunkTwo(context, packageName)
                    val totalSize = child.sumOf { it.size }
                    if (totalSize > 0) {
                        scanList.add(
                            JunkBean(
                                "", appName, totalSize, childList = child, drawable = icon
                            )
                        )
                    }
                } else {
                    val AppDataPath = "/Android/data"
                    val path =
                        Environment.getExternalStorageDirectory().absolutePath + AppDataPath + "/" + packageName + "/cache"
                    val file = FileUtils.getFileByPath(path)
                    if (FileUtils.isFileExists(file) && FileUtils.isDir(file) && file.length() > 0L && file.listFiles()
                            ?.isNotEmpty() == true
                    ) {
                        val fileList = ArrayList<ChildBean>()
                        val list = FileUtils.listFilesInDir(file)
                        list.forEach {
                            if (it.canWrite()) {
                                fileList.add(ChildBean(it.absolutePath, FileUtils.getLength(it)))
                            }
                        }
                        val totalSize = fileList.sumOf { it.size }
                        if (totalSize > 0) {
                            scanList.add(
                                JunkBean(
                                    "", appName, totalSize, childList = fileList, drawable = icon
                                )
                            )
                        }
                    }
                }
            }

            val t2 = (3000 - (System.currentTimeMillis() - time))
            val time = if (t2 > 0) {
                t2
            } else {
                0
            }
            delay(time)
            withContext(Dispatchers.Main) {
                callBack.invoke(scanList)
            }
        }
    }

    private fun scanCacheJunkTwo(
        context: Context, packageName: String
    ): ArrayList<ChildBean> {
        val fileList = arrayListOf<ChildBean>()
        val projection = arrayOf(
            MediaStore.Files.FileColumns._ID,
            MediaStore.Files.FileColumns.DISPLAY_NAME,
            MediaStore.Files.FileColumns.DATE_ADDED,
            MediaStore.Files.FileColumns.DATA,
            MediaStore.Files.FileColumns.SIZE
        )
        val selection =
            "(${MediaStore.Files.FileColumns.OWNER_PACKAGE_NAME} = '$packageName' " + "and ${MediaStore.Files.FileColumns.DATA} LIKE '%Cache%' )"

        context.contentResolver.query(
            MediaStore.Files.getContentUri("external"), projection, selection, null, null
        )?.use { cursor ->
            val displaySizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE)
            val dataindex: Int = cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA)
            while (cursor.moveToNext()) {
                val displaySize = cursor.getLong(displaySizeColumn)
                val path: String = cursor.getString(dataindex)
                if (!path.isNullOrEmpty()) {
                    var file = File(path)
                    if (file.canWrite()) {
                        if (FileUtils.isFile(path) && displaySize > 0L) {
                            fileList.add(ChildBean(path, 0L))
                        }
                    }
                }
            }
        }
        return fileList
    }


    private fun loadPhotos(
        context: Context?,
        lifecycleScope: LifecycleCoroutineScope,
        callBack: (images: ArrayList<JunkBean>) -> Unit
    ) {
        val mMediaList = arrayListOf<JunkBean>()
        var cursor: Cursor? = null
        if (context != null) {
            val selectionArgs = arrayOf("image/jpeg", "image/png")
            val sortOrder = MediaStore.Images.Media.DATE_MODIFIED + " DESC "

            lifecycleScope.launch(Dispatchers.IO) {
                try {
                    cursor = context.contentResolver.query(
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                        null,
                        MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?",
                        selectionArgs,
                        sortOrder
                    )
                    cursor?.let {
                        if (it.moveToFirst()) {
                            do {
                                val path =
                                    it.getString(it.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))
                                val size =
                                    it.getLong(it.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE))
                                val truePath =
                                    it.getString(it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))

                                val id =
                                    it.getLong(it.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
                                val uri = ContentUris.withAppendedId(
                                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id
                                )
                                val title =
                                    it.getString(it.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE))

                                if (size > 2 * 1024 * 1024) {
                                    val file = File(path)
//                                    if (file != null && file.exists() && file.isFile && file.canWrite()) {
                                    if (file != null && file.exists() && file.isFile) {
                                        val lage = JunkBean(path, title, size, false, uri = uri)
                                        mMediaList.add(lage)
                                    }
                                }

                            } while (it.moveToNext())
                        }
                        withContext(Dispatchers.Main) {
                            callBack.invoke(mMediaList)
                        }
                    } ?: kotlin.run {
                        withContext(Dispatchers.Main) {
                            callBack.invoke(mMediaList)
                        }
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                    withContext(Dispatchers.Main) {
                        callBack.invoke(mMediaList)
                    }
                } finally {
                    if (cursor != null && cursor?.isClosed != true) {
                        cursor?.close()
                        cursor = null
                    }
                }
            }
        }
    }

    private fun loadVideos(
        context: Context?,
        lifecycleScope: LifecycleCoroutineScope,
        callBack: (images: ArrayList<JunkBean>) -> Unit
    ) {
        val mMediaList = arrayListOf<JunkBean>()
        var cursor: Cursor? = null
        if (context != null) {
            try {
                lifecycleScope.launch(Dispatchers.IO) {
                    cursor = context.contentResolver.query(
                        MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                        null,
                        MediaStore.Video.Media.DURATION + ">=0",
                        null,
                        MediaStore.Video.Media.DATE_MODIFIED + " DESC"
                    )
                    cursor?.let {
                        if (it.moveToFirst()) {
                            do {
                                val path =
                                    it.getString(it.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))
                                val lastModified =
                                    it.getLong(it.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED))
                                val size =
                                    it.getLong(it.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE))

                                val truePath =
                                    it.getString(it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))

                                val id =
                                    it.getLong(it.getColumnIndexOrThrow(MediaStore.Video.Media._ID))
                                val uri = ContentUris.withAppendedId(
                                    MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id
                                )
                                val duration =
                                    it.getLong(it.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION))
                                val title =
                                    it.getString(it.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE))
                                if (size > 2 * 1024 * 1024) {
                                    val file = File(path)
                                    if (file != null && file.exists() && file.isFile && file.canWrite()) {
                                        val lage = JunkBean(path, title, size, false, uri = uri)
                                        mMediaList.add(lage)
                                    }
                                }

                            } while (it.moveToNext())
                        }
                        withContext(Dispatchers.Main) {
                            callBack.invoke(mMediaList)
                        }
                    } ?: kotlin.run {
                        withContext(Dispatchers.Main) {
                            callBack.invoke(mMediaList)
                        }
                    }
                }

            } catch (e: Exception) {
                e.printStackTrace()
                callBack.invoke(mMediaList)
            } finally {
                if (cursor != null && cursor?.isClosed != true) {
                    cursor?.close()
                    cursor = null
                }
            }
        }
    }

}